﻿using MidiSharp;
using MidiSharp.Events.Meta;
using MidiSharp.Events.Meta.Text;
using SoundEngine3.DspPlugins.Meta;
using SoundEngine3.SequenceContent;
using System;
using System.IO;
using ZquenceStudio3.Core.Formats;
using ZquenceStudio3.Core.Album;

namespace MidiFormats
{
    public class MidiExporterFormat : BaseExportFormat
    {
        public override string[] AlbumFilenameType
        {
            get
            {
                return new string[] { };
            }
        }

        public override string AlbumFormatName
        {
            get
            {
                return string.Empty;
            }
        }


        public override string[] SongFilenameType
        {
            get
            {
                return new string[] { ".mid" };
            }
        }

        public override string SongFormatName
        {
            get
            {
                return "MIDI";
            }
        }
        public override Version FormatVersion
        {
            get
            {
                return new Version(1, 0, 0, 0);
            }
        }

        private ExportMidiType mImportType;

        enum ExportMidiType
        {
            PatternsOnly_MultiTrack,
            Song_MultiTrack,
            //Song_SingleTrack
        }

        public MidiExporterFormat()
        {
            // Buffer size.
            EnumParameter<ExportMidiType> exportMidiType = new EnumParameter<ExportMidiType>(null);
            exportMidiType.OnValueChanged += delegate
            {
                mImportType = exportMidiType.EnumValue;
            };
            exportMidiType.Label = "Export type";
            AddSetting(exportMidiType);
        }

        public override bool FileSupported(Stream stream)
        {
            return true;
        }

        protected override void SaveAlbum(AlbumSolutionFile root, string path, FormatProgress progress)
        {
            throw new NotImplementedException();
        }

        protected override void SaveSong(Song song, FormatProgress progress)
        {
            string filePath = song.FilePath;

            int maxProgress = 0;

            MidiSharp.Format trackType = Format.One;
            switch (mImportType)
            {
                case ExportMidiType.PatternsOnly_MultiTrack:
                    trackType = Format.One;
                    break;
                case ExportMidiType.Song_MultiTrack:
                    trackType = Format.One;
                    break;
                //case ExportMidiType.Song_SingleTrack:
                //    trackType = Format.Zero;
                //    break;
                default:
                    throw new NotImplementedException();
            }

            MidiSharp.MidiSequence midiFile = new MidiSharp.MidiSequence(trackType, (int)Context.SoundEngine.Sequencer.Time.PpqResolution);

            // Render song.
            // Last note inclusiv his length.
            int lastNoteOffset = 0;
            foreach (Reference reference in Context.SoundEngine.Sequencer.References)
            {
                if (lastNoteOffset < reference.Start * 8 + reference.Length * 8)
                {
                    lastNoteOffset = (int)(reference.Start * 8 + reference.Length * 8);
                }
            }

            // Calculate the max progress.
            foreach (var pattern in Context.SoundEngine.Sequencer.Patterns)
            {
                foreach (var sequence in pattern.Sequences)
                {
                    foreach (var note in sequence.Notes)
                    {
                        ++maxProgress;
                    }
                }
            }

            if (mImportType == ExportMidiType.PatternsOnly_MultiTrack)
            {
                WritePatternOnly(midiFile);
            }
            else
            {
                WriteSong(midiFile);
            }

            try
            {
                using (var stream = SharpDepend.Manager.FileManager.CreateFile(filePath))
                {
                    midiFile.Save(stream);
                }
            }
            catch (IOException e)
            {
                Problems.AddProblem("Error while saving midi file '" + e.Message + "'");
            }
        }

        private void WritePatternOnly(MidiSequence midiFile)
        {
            // Render only patterns.
            int currentLastNoteOffset = 0;
            foreach (Pattern pattern in Context.SoundEngine.Sequencer.Patterns)
            {
                // Loop all sequences.
                foreach (var sequence in pattern.Sequences)
                {
                    MidiTrack trackdata = midiFile.Tracks.AddNewTrack();
                    //trackdata.Events.Add(new CopyrightTextMetaMidiEvent(0, "Copyright"));
                    //trackdata.Events.Add(new CuePointTextMetaMidiEvent(0, "CuePoint"));
                    //trackdata.Events.Add(new DeviceNameTextMidiEvent(0, "DeviceName"));
                    //trackdata.Events.Add(new InstrumentTextMetaMidiEvent(0, "Instrument"));
                    //trackdata.Events.Add(new LyricTextMetaMidiEvent(0, "Lyric"));
                    //trackdata.Events.Add(new MarkerTextMetaMidiEvent(0, "Marker"));
                    //trackdata.Events.Add(new ProgramNameTextMetaMidiEvent(0, "ProgramName"));
                    trackdata.Events.Add(new SequenceTrackNameTextMetaMidiEvent(0, pattern.Name));
                    //trackdata.Events.Add(new TextMetaMidiEvent(0, "Text"));

                    //trackdata.Events.Add(new ChannelPrefixMetaMidiEvent(0, 17));
                    //trackdata.Events.Add(new KeySignatureMetaMidiEvent(0, Key.Flat4, Tonality.Minor));
                    //trackdata.Events.Add(new MidiPortMetaMidiEvent(0, 0));
                    //trackdata.Events.Add(new ProprietaryMetaMidiEvent(3, new byte[] { 0, 1, 2, 3 }));
                    //trackdata.Events.Add(new SequenceNumberMetaMidiEvent(4, 123));
                    ////trackdata.Events.Add(new SMPTEOffsetMetaMidiEvent(0, 1, 2, 3, 4, 5));
                    //trackdata.Events.Add(new TempoMetaMidiEvent(0, 123));
                    //trackdata.Events.Add(new TimeSignatureMetaMidiEvent(0, 4, 4, 64, 4));

                    trackdata.Events.Add(new TempoMetaMidiEvent(0, (int)Context.SoundEngine.Sequencer.Time.Tempo));

                    currentLastNoteOffset = 0;

                    int length = (int)sequence.GetLength();
                    for (int i = 0; i < length; i++)
                    {
                        // Sparar dom sorterade noterna i Midi-filen.
                        foreach (Note note in sequence.Notes)
                        {
                            int delta = i - currentLastNoteOffset;

                            if ((int)note.Start == i)
                            {
                                // Note on.
                                trackdata.Events.Add(new MidiSharp.Events.Voice.Note.OnNoteVoiceMidiEvent(delta, 0, note.NoteKey, note.Velocity));
                                //trackdata.noteOn(delta, note.NoteKey, (int)(note.Velocity));
                                currentLastNoteOffset = i;
                            }
                            if ((int)(note.Start + note.Length) == i)
                            {
                                // Note off.
                                trackdata.Events.Add(new MidiSharp.Events.Voice.Note.OffNoteVoiceMidiEvent(delta, 0, note.NoteKey, note.Velocity));
                                //trackdata.noteOff(delta, note.NoteKey);
                                currentLastNoteOffset = i;
                            }
                        }
                    }

                    // End of track.
                    trackdata.Events.Add(new MidiSharp.Events.Meta.EndOfTrackMetaMidiEvent(0));
                }
            }
        }

        private void WriteSong(MidiSequence midiFile)
        {
            // Render only patterns.
            int currentLastNoteOffset = 0;
            foreach (Pattern pattern in Context.SoundEngine.Sequencer.Patterns)
            {
                // Loop all sequences.
                foreach (var sequence in pattern.Sequences)
                {
                    MidiTrack trackdata = midiFile.Tracks.AddNewTrack();
                    trackdata.Events.Add(new SequenceTrackNameTextMetaMidiEvent(0, pattern.Name));

                    trackdata.Events.Add(new TempoMetaMidiEvent(0, (int)Context.SoundEngine.Sequencer.Time.Tempo));

                    // Loop all references for the track.
                    foreach (var reference in pattern.BelongsToReferences)
                    {
                        currentLastNoteOffset = 0;

                        int length = (int)sequence.GetLength();
                        for (int i = 0; i < length; i++)
                        {
                            // Sparar dom sorterade noterna i Midi-filen.
                            foreach (Note note in sequence.Notes)
                            {
                                int delta = i - currentLastNoteOffset;

                                int noteStart = (int)(note.Start + reference.Start);
                                int noteLength = (int)(note.Length + noteStart);

                                if (noteStart == i)
                                {
                                    // Note on.
                                    trackdata.Events.Add(new MidiSharp.Events.Voice.Note.OnNoteVoiceMidiEvent(delta, 0, note.NoteKey, note.Velocity));
                                    currentLastNoteOffset = i;
                                }
                                if (noteLength == i)
                                {
                                    // Note off.
                                    trackdata.Events.Add(new MidiSharp.Events.Voice.Note.OffNoteVoiceMidiEvent(delta, 0, note.NoteKey, note.Velocity));
                                    currentLastNoteOffset = i;
                                }
                            }
                        }
                    }
                    
                    // End of track.
                    trackdata.Events.Add(new MidiSharp.Events.Meta.EndOfTrackMetaMidiEvent(0));
                }
            }
        }
    }
}
